﻿using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections.Generic;
using UnityEngine.Serialization;

namespace ProjectTW.Foliage
{
    [ExecuteInEditMode]
    public sealed class FoliageManager : FoliageMeshManager
    {
        public const int Foliage_Area_Radius = 10240;  // -10240 -> 10240 (x & y) [20,480 * 20,480 = 419,430 units] 
        public const int Foliage_Area_Resolution = 40; // 40 res -> 10240 * 2 = 20480 / 40 = 512. 

        public const int Foliage_Instance_Area_Size = (Foliage_Area_Radius * 2) / Foliage_Area_Resolution;       // 512
        public const int Foliage_Instance_Area_Bounds = Foliage_Instance_Area_Size * Foliage_Instance_Area_Size; // 512 * 512

        public static Vector3 FoliageMainAreaSectorSize = new Vector3(Foliage_Area_Radius * 2, 0, Foliage_Area_Radius * 2);
        public static Vector3 FoliageMainAreaBoundsMin = new Vector3(0, 0, 0);
        public static Vector3 FoliageMainAreaBoundsMax = new Vector3(Foliage_Area_Radius, Foliage_Area_Radius, Foliage_Area_Radius);
        public static Bounds FoliageMainAreaBounds = new Bounds(FoliageMainAreaBoundsMin, FoliageMainAreaBoundsMax * 2);

        public static Vector3 FoliageInstanceAreaBoundsMin = new Vector3(0, 0, 0);
        public static Vector3 FoliageInstanceAreaBoundsMax = new Vector3(Foliage_Instance_Area_Size, Foliage_Instance_Area_Size, Foliage_Instance_Area_Size);

        public static List<FoliageMapBase> FoliageMapsWaitingForSave = new List<FoliageMapBase>();

        private static int s_WarmUpLastFrame = -1;
        private static FoliageManager s_Instance;

        public static FoliageManager Instance
        {
            get
            {
                return s_Instance;
            }
            private set
            {
                s_Instance = value;
            }
        }

        [SerializeField]
        private bool _Enabled = true;
        [SerializeField]
        private float m_Density = 1;
        [SerializeField]
        private int m_GlobalFadeDistance = 100;
        [SerializeField]
        private int m_FoliageGenerationLayerMask = 1;
        [SerializeField]
        private Color m_FoliageGlobalTint = Color.white;

        public bool UseQualitySettingsShadowDistance = false;
        public float FoliageShadowDistance = 100;

        [SerializeField]
        private FoliageSector m_Sector = null;

        public new bool Enabled
        {
            get
            {
                return _Enabled;
            }
            set
            {
                _Enabled = value;
            }
        }

        public float Density
        {
            get
            {
                return m_Density;
            }
            set
            {
                value = Mathf.Clamp(value, 0, 1);

                if (m_Density != value)
                {
                    m_Density = value;
                    FoliageDB.Instance.UpdateShaderGeneralSettings();
                    FoliageMeshManager.MarkDensitiesDirty();
                }
            }
        }

        public int GlobalFadeDistance
        {
            get
            {
                return m_GlobalFadeDistance;
            }
            set
            {
                if (m_GlobalFadeDistance != value)
                {
                    m_GlobalFadeDistance = value;
                    GenerateFoliageMeshInstances();
                    RegenerateQueueInstances();
                    FoliageDB.Instance.UpdateShaderGeneralSettings();
                }
            }
        }

        public int FoliageGenerationLayerMask
        {
            get
            {
                return m_FoliageGenerationLayerMask;
            }
            set
            {
                if (m_FoliageGenerationLayerMask != value)
                {
                    m_FoliageGenerationLayerMask = value;
#if UNITY_EDITOR
                    if (!Application.isPlaying && EditorUtility.DisplayDialog("Generation Masks", "Apply generation masks changes to manager instances?", "Yes", "No"))
                    {
                        FoliageWorldMaps.ReGenerateGlobally();
                    }
#endif
                }
            }
        }

        public Color FoliageGlobalTint
        {
            get
            {
                return m_FoliageGlobalTint;
            }
            set
            {
                if (m_FoliageGlobalTint != value)
                {
                    m_FoliageGlobalTint = value;
                    FoliageDB.Instance.UpdateShaderGeneralSettings();
                }
            }
        }

        public FoliageSector Sector
        {
            get
            {
                if (m_Sector == null)
                {
                    m_Sector = SectorBase.GenerateSector<FoliageSector, FoliageChunk>(transform, FoliageMainAreaSectorSize, m_Sector, Foliage_Area_Resolution);
                }

                return m_Sector;
            }
        }

        /// <summary>
        /// Get chunk from bounds.
        /// 
        /// [REMOVE MAIN MANAGER POSITION FROM CORDS!!]
        /// for example:
        /// cordX = transform.position.x - FoliageCore_MainManager.instance.transform.position.x.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="z"></param>
        /// <returns></returns>
        public int GetChunkID(float x, float z)
        {
            return Mathf.FloorToInt(x / Foliage_Instance_Area_Size) + Mathf.FloorToInt(z / Foliage_Instance_Area_Size) * Foliage_Area_Resolution;
        }

        /// <summary>
        /// Check if the chunk id is in range
        /// </summary>
        /// <param name="chunkID"></param>
        /// <returns></returns>
        public bool CheckChunkInBounds(int chunkID)
        {
            return chunkID >= 0 && chunkID < (Foliage_Area_Resolution * Foliage_Area_Resolution);
        }

        protected override void Awake()
        {
            base.Awake();
            transform.position = new Vector3(-Foliage_Area_Radius, 0, -Foliage_Area_Radius); // put this at the negative radius to create a balance.
        }

        protected override void OnEnable()
        {
            base.OnEnable();
            Instance = this;

            if (Sector == null)
            {
            } // call this to force the sector to be created if not available.

            FoliagePrototype.OnFoliageEnabledStateChangedEvent += OnFoliagePrototypeChanged;
            FoliageDB.Instance.UpdateShaderGeneralSettings();
        }

        protected override void OnDisable()
        {
            base.OnDisable();
            FoliagePrototype.OnFoliageEnabledStateChangedEvent -= OnFoliagePrototypeChanged;
        }

        private void OnDestroy()
        {
            if (s_Instance == this)
            {
                s_Instance = null;
            }
        }

        /// <summary>
        /// Called when the enable state of a prototype is changed
        /// </summary>
        /// <param name="changedPrototype"></param>
        /// <param name="value"></param>
        private void OnFoliagePrototypeChanged(FoliagePrototype changedPrototype, bool value)
        {
            if (value)
            {
                foreach (var meshInstances in PrototypeMeshInstances)
                {
                    GenerateFoliageMeshInstanceForIndex(changedPrototype.Id, meshInstances.Key);
                }

                WarmUpDensityMaps();
            }
            else
            {
                DestroyMeshInstance(changedPrototype.Id);
            }

            RegenerateQueueInstances();
        }

        protected override void Update()
        {
            if (Enabled)
            {
                base.Update();
            }
        }

        /// <summary>
        /// 拷贝地形细节对象到自定义细节对象
        /// </summary>
        /// <param name="terrain"></param>
        public void InsertFoliageFromTerrain(Terrain terrain)
        {
            TerrainData terrainData = terrain.terrainData;
            float terrainPositionX = terrain.transform.position.x;
            float terrainPositionZ = terrain.transform.position.z;
            float terrainSize = terrainData.size.x;
            FoliageSector sector = this.Sector;
            int resolution = terrainData.detailWidth;
            float terrainConvertionRatio = terrainSize / resolution;
            float terrainAdjuster = terrainConvertionRatio > 1 ? terrainConvertionRatio : 1f;
            List<int[,]> details = FoliageStandaloneUtility.GetTerrainDetails(terrainData);
            FoliagePrototype[] prototypes = FoliageStandaloneUtility.AddPrototypesIfDontExist(terrainData.detailPrototypes);
            WarmUpDensityMaps(true);
            float managerInstancePositionX = transform.position.x;
            float managerInstancePositionZ = transform.position.z;
            Debug.Log("Copying from terrain. \nSettings: " + terrainAdjuster + " " + terrainConvertionRatio + " " + resolution);

            try
            {
                for (int x = 0; x < resolution; x++)
                {
                    for (int z = 0; z < resolution; z++)
                    {
                        float initWorldX = x * terrainConvertionRatio + terrainPositionX;
                        float initWorldZ = z * terrainConvertionRatio + terrainPositionZ;

                        for (var worldX = initWorldX; worldX < initWorldX + terrainAdjuster; worldX++)
                        {
                            for (var worldZ = initWorldZ; worldZ < initWorldZ + terrainAdjuster; worldZ++)
                            {
                                var mChunk = sector.FoliageChunks[GetChunkID(worldX - managerInstancePositionX, worldZ - managerInstancePositionZ)];
                                var mInstance = mChunk.GetOrCreateFoliageManagerInstance();
                                var localX = mInstance.TransformCord(worldX, mInstance.Pos.x);
                                var localZ = mInstance.TransformCord(worldZ, mInstance.Pos.z);
                                var mInstanceIndex = localX + localZ * mInstance.FoliageAreaResolutionIntegral;
                                FoliageDensityMap densityMap = mInstance.DensityMap;

                                for (int prototypeIndex = 0; prototypeIndex < prototypes.Length; prototypeIndex++)
                                {
                                    byte prototype = (byte) prototypes[prototypeIndex].Id;
                                    densityMap.SetPrototypeDensity(mInstanceIndex, prototype, (byte) details[prototypeIndex][z, x]);
                                    densityMap.SetPixels32Delayed();
                                }
                            }
                        }

                        for (int prototypeIndex = 0; prototypeIndex < prototypes.Length; prototypeIndex++)
                        {
                            details[prototypeIndex][z, x] = 0;
                        }
                    }
                }

                for (int prototypeIndex = 0; prototypeIndex < prototypes.Length; prototypeIndex++)
                {
                    terrainData.SetDetailLayer(0, 0, prototypeIndex, details[prototypeIndex]);
                }

                SaveDelayedMaps();
                RegenerateQueueInstances();

                if (!FoliageGenerationLayerMask.isBitMasked(terrain.gameObject.layer))
                {
#if UNITY_EDITOR
                    if (EditorUtility.DisplayDialog("Add Layer",
                            "Terrain layer isn't included in the heights layers. That means that it will ignore the height mask of the terrain. \nWould you like uNature to automatically add it for you?",
                            "Yes", "No"))
                    {
                        FoliageGenerationLayerMask |= (1 << terrain.gameObject.layer);
                    }
#endif
                }
            }
            catch (System.Exception ex)
            {
                Debug.LogError(ex.ToString());
            }
        }
        
        public static bool CheckCordsOutOfBounds(float x, float z, float sizeX, float sizeZ)
        {
            bool outOfBounds = x < -Foliage_Area_Radius || z < -Foliage_Area_Radius || (x + sizeX) > Foliage_Area_Radius || (z + sizeZ) > Foliage_Area_Radius;
            return outOfBounds;
        }

        /// <summary>
        /// 通过指定分辨率获取原型模型实例
        /// </summary>
        /// <param name="resolution"></param>
        /// <returns></returns>
        public static Dictionary<int, FoliageGPUMesh> GetPrototypeMeshInstances(FoliageResolutions resolution)
        {
            if (!PrototypeMeshInstances.ContainsKey(resolution))
            {
                GenerateFoliageMeshInstances(resolution);
            }

            return PrototypeMeshInstances[resolution];
        }

        /// <summary>
        /// 延迟保存地图信息
        /// </summary>
        public static void SaveDelayedMaps()
        {
            for (int i = 0; i < FoliageMapsWaitingForSave.Count; i++)
            {
                FoliageMapsWaitingForSave[i].ApplySetPixelsDelayed();
            }

            FoliageMapsWaitingForSave.Clear();
        }

        /// <summary>
        /// 更新现有草的地图
        /// </summary>
        /// <param name="prototype"></param>
        public static void WarmUpDensityMaps(bool advancedPoppulation_MeshInstances_MapPixels = false)
        {
            if (Instance == null)
            {
                return;
            }

            List<FoliageChunk> chunks = Instance.Sector.FoliageChunks;
            FoliageChunk chunk;
            FoliageInstanceObject foliageInstance;

            for (int i = 0; i < chunks.Count; i++)
            {
                chunk = chunks[i];

                if (!chunk.IsFoliageInstanceAttached)
                {
                    continue; // if no manager instance attached then there's nothing to remove!.
                }

                foliageInstance = chunk.GetOrCreateFoliageManagerInstance();
                foliageInstance.UpdateDensityMapsForMaterials(advancedPoppulation_MeshInstances_MapPixels);

                if (advancedPoppulation_MeshInstances_MapPixels && foliageInstance.MeshInstances == null)
                {
                } // warm up mesh instances if enabled.
            }
        }

        /// <summary>
        /// 更新存在地图草密度图
        /// </summary>
        /// <param name="prototype"></param>
        public static void WarmUpDensityMaps(FoliageChunk[] chunks, bool advancedPoppulation_MeshInstances_MapPixels = false)
        {
            if (Instance == null)
            {
                return;
            }

            FoliageChunk chunk;
            FoliageInstanceObject foliageInstance;
            int currentFrame = Time.frameCount;
            // 如果不是当前帧数则更新
            if (currentFrame != s_WarmUpLastFrame)
            {
                for (int i = 0; i < chunks.Length; i++)
                {
                    chunk = chunks[i];

                    // 是否细节对象实例（空节点）
                    if (chunk == null || !chunk.IsFoliageInstanceAttached)
                    {
                        continue; // if no manager instance attached then there's nothing to remove!.
                    }

                    foliageInstance = chunk.GetOrCreateFoliageManagerInstance();
                    foliageInstance.UpdateDensityMapsForMaterials(advancedPoppulation_MeshInstances_MapPixels);

                    // MeshInstance为空会触发mesh合并
                    if (advancedPoppulation_MeshInstances_MapPixels && foliageInstance.MeshInstances == null)
                    {
                    } // warm up mesh instances if enabled.
                }

                s_WarmUpLastFrame = currentFrame;
            }
        }

        /// <summary>
        /// 重置已存在的草map图
        /// </summary>
        /// <param name="prototype"></param>
        public static void ResetDensityMaps()
        {
            if (Instance == null)
            {
                return;
            }

            List<FoliageChunk> chunks = Instance.Sector.FoliageChunks;

            for (int i = 0; i < chunks.Count; i++)
            {
                FoliageChunk chunk = chunks[i];

                if (!chunk.IsFoliageInstanceAttached)
                {
                    continue;
                }

                FoliageInstanceObject foliageInstance = chunk.AttachedFoliageInstance;
                foliageInstance.DensityMap.ResetDensity();
            }
        }

        /// <summary>
        /// 创建管理实例
        /// </summary>
        public static void InitializeOrCreate()
        {
            if (Instance != null)
            {
                return;
            }

            GameObject instanceGO = new GameObject("FoliageManager [DESTROY ONLY]");
            instanceGO.AddComponent<FoliageManager>();
#if UNITY_EDITOR
            UnityEditor.SceneManagement.EditorSceneManager.MarkAllScenesDirty();
#endif
        }

        /// <summary>
        /// 删除管理器实例
        /// </summary>
        public static void DestroyManager()
        {
            FoliageChunk chunk;
            FoliageInstanceObject foliageInstance;

            try
            {
                for (int i = 0; i < Instance.Sector.FoliageChunks.Count; i++)
                {
                    chunk = Instance.Sector.FoliageChunks[i];

                    if (chunk.IsFoliageInstanceAttached)
                    {
                        foliageInstance = chunk.GetOrCreateFoliageManagerInstance();
                        FoliageInstanceObject.CleanUp(foliageInstance);
                    }
                }
            }
            catch (System.Exception ex)
            {
                Debug.LogError("Error occured while cleaning manager instances. \n" + ex.ToString());
            }

            GameObject.DestroyImmediate(Instance.Sector.gameObject);
            GameObject.DestroyImmediate(Instance.gameObject);
            s_Instance = null;
#if UNITY_EDITOR
            UnityEditor.SceneManagement.EditorSceneManager.MarkAllScenesDirty();
#endif
        }
    }
}
